import { Controller, Post, Body, HttpStatus, HttpCode, NotFoundException, BadRequestException, ForbiddenException, InternalServerErrorException, Get, Param } from "@nestjs/common";
import { ApiOkResponse } from "@nestjs/swagger";
import { UserService } from "src/modules/user/user.service";
import { UserEntity } from "src/modules/user/user.entity";
import { Response } from "src/utils/response/response.decorator";
import { IResponse } from "src/utils/response/response.interface";
import { ENUM_STATUS_CODE_ERROR } from "src/utils/error/error.constant";
import { DebuggerService } from "src/debugger/service/debugger.service";
import { ENUM_USER_STATUS_CODE_ERROR } from "src/modules/user/user.constant";
import { AuthLoginDto } from "./dto/auth.login.dto";
import { AuthChangePasswordDto } from "./dto/auth.change-password.dto";
import { AuthService } from "./auth.service";
import { AuthJwtGuard, AuthRefreshJwtGuard, Token, User } from "./auth.decorator";
import { ENUM_AUTH_STATUS_CODE_ERROR, ENUM_AUTH_STATUS_CODE_SUCCESS, LOGIN_TYPE } from "./auth.constant";
import { AuthSignUpDto } from "./dto/auth.sign-up.dto";
import { IUserCheckExist } from "src/modules/user/user.interface";
import { UserDetailSerialization } from "src/modules/user/serialization/user.detail.serialization";

@Controller({
  version: "1",
  path: "/auth",
})
export class AuthController {
  constructor(
    private readonly debuggerService: DebuggerService,
    private readonly userService: UserService,
    private readonly authService: AuthService,
  ) {}

  @Response("auth.login", 200)
  @HttpCode(HttpStatus.OK)
  @Post("/login")
  @ApiOkResponse({
    type: AuthLoginDto,
    description: 'User info with access token',
  })
  async loginWithName(@Body() { rememberMe, name, password, type, phone, captchaCode, captchaId }: AuthLoginDto): Promise<IResponse> {
    rememberMe = rememberMe ? true : false;
    // 验证码校验
    if(type === LOGIN_TYPE.NAME) {
      const svgCaptchaResult = await this.authService.validateSvgCaptcha(captchaCode, captchaId)
      if(!svgCaptchaResult) {
        this.debuggerService.error("Authorized error captcha validation failed", "AuthController", "login");

        throw new BadRequestException({
          statusCode: ENUM_USER_STATUS_CODE_ERROR.USER_CAPTCHA_ERROR,
          message: "user.error.captcha",
        });
      }
    }
    const user = type === LOGIN_TYPE.NAME ? 
      await this.userService.findByName(name) : 
      await this.userService.findByPhone(phone);

    if (!user) {
      this.debuggerService.error("Authorized error user not found", "AuthController", "login");

      throw new BadRequestException({
        statusCode: ENUM_USER_STATUS_CODE_ERROR.USER_NOT_FOUND_ERROR,
        message: "user.error.notFound",
      });
    }

    const validate: boolean = type === LOGIN_TYPE.NAME ? 
      await this.authService.validateUser(password, user.password) :
      await this.authService.validateSMSCaptcha(captchaCode, phone)

    if (!validate) {
      this.debuggerService.error("Authorized error", "AuthController", "login");

      throw new BadRequestException({
        statusCode: ENUM_AUTH_STATUS_CODE_ERROR.AUTH_PASSWORD_NOT_MATCH_ERROR,
        message: "auth.error.passwordNotMatch",
      });
    } else if (user.status === 'locked') {
      this.debuggerService.error("Auth Block", "AuthController", "login");

      throw new ForbiddenException({
        statusCode: ENUM_USER_STATUS_CODE_ERROR.USER_IS_INACTIVE_ERROR,
        message: "user.error.inactive",
      });
    }
    delete user.password;
    const payloadAccessToken: Record<string, any> = await this.authService.createPayloadAccessToken(user, rememberMe);
    const payloadRefreshToken: Record<string, any> = await this.authService.createPayloadRefreshToken(user, rememberMe, {
      loginDate: payloadAccessToken.loginDate,
    });

    const accessToken: string = await this.authService.createAccessToken(payloadAccessToken);

    const refreshToken: string = await this.authService.createRefreshToken(payloadRefreshToken, rememberMe);

    return {
      accessToken,
      refreshToken,
    };
  }

  @Response("auth.refresh")
  @AuthRefreshJwtGuard()
  @HttpCode(HttpStatus.OK)
  @Post("/refresh")
  async refresh(
    @User() { id, rememberMe, loginDate }: Record<string, any>,
    @Token() refreshToken: string
  ): Promise<IResponse> {
    const user: UserEntity = await this.userService.findById(id)

    if (!user) {
      this.debuggerService.error("Authorized error user not found", "AuthController", "refresh");

      throw new NotFoundException({
        statusCode: ENUM_USER_STATUS_CODE_ERROR.USER_NOT_FOUND_ERROR,
        message: "user.error.notFound",
      });
    } else if (user.status === 'locked') {
      this.debuggerService.error("Auth Block", "AuthController", "refresh");

      throw new ForbiddenException({
        statusCode: ENUM_USER_STATUS_CODE_ERROR.USER_IS_INACTIVE_ERROR,
        message: "user.error.inactive",
      });
    }

    const payloadAccessToken: Record<string, any> = await this.authService.createPayloadAccessToken(user, rememberMe, {
      loginDate,
    });

    const accessToken: string = await this.authService.createAccessToken(payloadAccessToken);

    return {
      accessToken,
      refreshToken,
    };
  }

  @Response("auth.changePassword")
  @AuthJwtGuard()
  @Post("/change-password")
  async changePassword(@Body() body: AuthChangePasswordDto, @User("id") id: string): Promise<void> {
    const user: UserEntity = await this.userService.findById(id);
    if (!user) {
      this.debuggerService.error("User not found", "AuthController", "changePassword");

      throw new NotFoundException({
        statusCode: ENUM_USER_STATUS_CODE_ERROR.USER_NOT_FOUND_ERROR,
        message: "user.error.notFound",
      });
    }

    const matchPassword: boolean = await this.authService.validateUser(body.oldPassword, user.password);
    if (!matchPassword) {
      this.debuggerService.error("Old password don't match", "AuthController", "changePassword");

      throw new BadRequestException({
        statusCode: ENUM_AUTH_STATUS_CODE_ERROR.AUTH_PASSWORD_NOT_MATCH_ERROR,
        message: "auth.error.passwordNotMatch",
      });
    }

    const newMatchPassword: boolean = await this.authService.validateUser(body.newPassword, user.password);
    if (newMatchPassword) {
      this.debuggerService.error("New password cant't same with old password", "AuthController", "changePassword");

      throw new BadRequestException({
        statusCode: ENUM_AUTH_STATUS_CODE_ERROR.AUTH_PASSWORD_NEW_MUST_DIFFERENCE_ERROR,
        message: "auth.error.newPasswordMustDifference",
      });
    }

    try {
      const password = await this.authService.createPassword(body.newPassword);

      await this.userService.updatePassword(user.id, password.passwordHash);
    } catch (e) {
      this.debuggerService.error("Change password error internal server error", "AuthController", "changePassword", e);

      throw new InternalServerErrorException({
        statusCode: ENUM_STATUS_CODE_ERROR.UNKNOWN_ERROR,
        message: "http.serverError.internalServerError",
      });
    }
    return;
  }

  @Response("auth.signUp")
  @Post("/sign-up")
  async signUp(@Body() { email, phone, name,  password }: AuthSignUpDto): Promise<IResponse> {
    console.log(email, phone, name,  password);
    
    const checkExist: IUserCheckExist = await this.userService.checkExist(email, phone);

    if (checkExist.email && checkExist.phone) {
      this.debuggerService.error("create user exist", "UserController", "create");

      throw new BadRequestException({
        statusCode: ENUM_USER_STATUS_CODE_ERROR.USER_EXISTS_ERROR,
        message: "user.error.exist",
      });
    } else if (checkExist.email) {
      this.debuggerService.error("create user exist", "UserController", "create");

      throw new BadRequestException({
        statusCode: ENUM_USER_STATUS_CODE_ERROR.USER_EMAIL_EXIST_ERROR,
        message: "user.error.emailExist",
      });
    } else if (checkExist.phone) {
      this.debuggerService.error("create user exist", "UserController", "create");

      throw new BadRequestException({
        statusCode: ENUM_USER_STATUS_CODE_ERROR.USER_MOBILE_NUMBER_EXIST_ERROR,
        message: "user.error.mobileNumberExist",
      });
    }

    try {
      const hashPassword = await this.authService.createPassword(password);

      const user = await this.userService.createUser({
        name,
        email,
        phone,
        role: 'visitor',
        password: hashPassword.passwordHash,
        avatar: ''
      });

      const payloadAccessToken: Record<string, any> = await this.authService.createPayloadAccessToken(user, false);
      const payloadRefreshToken: Record<string, any> = await this.authService.createPayloadRefreshToken(user, false, {
        loginDate: payloadAccessToken.loginDate,
      });

      const accessToken: string = await this.authService.createAccessToken(payloadAccessToken);

      const refreshToken: string = await this.authService.createRefreshToken(payloadRefreshToken, false);

      return {
        accessToken,
        refreshToken,
      };
    } catch (err: any) {
      this.debuggerService.error("Signup try catch", "AuthController", "signUp", err);

      throw new InternalServerErrorException({
        statusCode: ENUM_STATUS_CODE_ERROR.UNKNOWN_ERROR,
        message: "http.serverError.internalServerError",
      });
    }
  }

  @Response("auth.userinfo")
  @AuthJwtGuard()
  @Get("/userinfo")
  async userinfo(@User("id") id: string): Promise<IResponse> {
    const user: UserEntity = await this.userService.findById(id);
    if (!user) {
      this.debuggerService.error("User not found", "AuthController", "userinfo");

      throw new NotFoundException({
        statusCode: ENUM_USER_STATUS_CODE_ERROR.USER_NOT_FOUND_ERROR,
        message: "user.error.notFound",
      });
    }

    const userData: UserDetailSerialization = await this.userService.serializationDetail(user)
    return userData
  }

  @Response("auth.logout")
  // @AuthJwtGuard()
  @HttpCode(HttpStatus.OK)
  @Post("/logout")
  async logout(): Promise<IResponse> {
    // TODO Logout Logistic
    return
  }

  @Response("auth.captcha")
  // @AuthJwtGuard()
  @HttpCode(HttpStatus.OK)
  @Get("/captcha")
  async captcha(): Promise<IResponse> {
    return this.authService.createCaptcha()
  }
}
